{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../common/rfsoc_book_banner.jpg\" alt=\"University of Strathclyde\" align=\"left\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Notebook Set E\n",
    "\n",
    "---\n",
    "\n",
    "## 03 - Complex Representation of QAM <a class=\"anchor\" id=\"complex_QAM\"></a>\n",
    "The QAM model we have looked at so far has used two real signals as the input waveform. A complex representation is also possible. This model is functionally identical to the previous model, however, it is simpler to implement as only a single mixer is required. In this notebook, we focus on demonstrating that the complex representation is equivalent to the original model, without going into the underlying maths in great detail.\n",
    "\n",
    "## Table of Contents\n",
    "* [1. Introduction](#introduction)\n",
    "* [2. Complex Modulation](#complex_QAM_mod)\n",
    "* [3. Complex Demodulation](#complex_QAM_demod)\n",
    "* [4. Conclusion](#conclusion)\n",
    "    \n",
    "## Revision\n",
    "* **v1.0** | 22/12/22 | *First Revision*\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Introduction <a class=\"anchor\" id=\"introduction\"></a>\n",
    "Consider a quadrature transmitter described using complex notation, as shown in Figure 1.\n",
    "\n",
    "<figure>\n",
    "<img src='./images/complex_modulation.svg' height='60%' width='60%'/>\n",
    "    <figcaption><b>Figure 1: Complex modulation architecture.</b></figcaption>\n",
    "</figure>\n",
    "\n",
    "Where:\n",
    "\n",
    "$$\n",
    "g(t) = g_{1}(t) + jg_{2}(t),\n",
    "$$\n",
    "\n",
    "and $g_{1}$ and $g_{2}$ are the same signals used in the QAM modulation example.\n",
    "\n",
    "$$\n",
    "g_{1}(t) = A_{1} \\cos(2 \\pi f_{b1} t)\n",
    "$$\n",
    "\n",
    "$$\n",
    "g_{2}(t) = A_{2} \\cos(2 \\pi f_{b2} t)\n",
    "$$\n",
    "\n",
    "For this notebook, we will use NumPy for computation and MatplotLib for visualisation of our waveforms. Let us begin by importing these libraries now."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Complex Modulation <a class=\"anchor\" id=\"complex_QAM_mod\"></a>\n",
    "First off, we define the two \"information\" signals, as seen in the previous notebook. The signals we use here to represent information are simple sine waves, which are not really carrying information. However, they are useful for the purposes of our example. We use the term \"In Phase\" for our real component and \"Quadrature\" for the imaginary component."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set basic params\n",
    "fs = 4096e6 # sample rate\n",
    "fb = 64e6 # frequency of baseband signal\n",
    "fb2 = 32e6 # frequency of second baseband signal\n",
    "A1 = 2 # baseband signal amplitude\n",
    "A2 = 1 # second baseband signal amplitude\n",
    "N_fft = 2048 # fft size\n",
    "\n",
    "t = np.arange(N_fft)/fs #time scale"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define two information signals\n",
    "g1 = A1*np.cos(2*np.pi*fb*t)\n",
    "g2 = A2*np.cos(2*np.pi*fb2*t)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We now define the complex signal $g(t)$ by summing together $g_{1}(t)$ and a complex signal, $g_{2}(t)$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define complex input signal using real g1(t) and imag. g2(t)\n",
    "g_complex = g1 + 1j*(g2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calculate FFT\n",
    "g_complex_fft_result = np.fft.fft(g_complex, N_fft)\n",
    "\n",
    "# Get the corresponding frequencies, that depend on N_fft and Fs - freq. domain x axis\n",
    "freqs = np.fft.fftfreq(N_fft,1/fs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we plot this complex information signal in the time domain, we can observe that the information signal matches $g_{1}$ and $g_{2}$, which was previously shown in the QAM example. \n",
    "\n",
    "We also show the spectra of the baseband signals as both a one sided and two sided frequency plot. Up to this point, we have only explored one sided plots, as our signals have been real, and exhibit symmetrical positive and negative frequencies. In this notebook, we will explore complex signals, which are non-symmetrical and contain information in both positive and negative frequencies.\n",
    "\n",
    "For the two sided frequency plot below, it was worth noting the symmetry that exists in the signal.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,3, figsize=(20,4))\n",
    "axs[0].plot(g_complex.real[:200])\n",
    "axs[0].plot(g_complex.imag[:200])\n",
    "axs[0].set_title('Time Domain Complex Information Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "axs[0].legend(('I signal Component', 'Q Signal Component'))\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(g_complex_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Complex Information Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()\n",
    "\n",
    "axs[2].plot(freqs[:int(N_fft)]/1e6, np.abs(g_complex_fft_result[:int(N_fft)]))\n",
    "axs[2].set_title('Two Sided FFT plot of the Complex Information Signal')\n",
    "axs[2].set_ylabel('Magnitude')\n",
    "axs[2].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This complex information signal can be modulated using a complex exponential at a 'frequency' of $f_{c}$ Hz. The equation below shows Euler's formula, which describes the relationship between a complex exponential and associated trigonometric functions.\n",
    "\n",
    "$$\n",
    "e^{j 2 \\pi f_{c} t} = \\cos (2 \\pi f_{c} t) + j \\sin (2 \\pi f_{c} t).\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fc = 1400e6 # Carrier Frequency\n",
    "\n",
    "# Define complex exponential used for modulation\n",
    "c_complex = np.exp(1j*2*np.pi*fc*t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of complex exponential\n",
    "c_complex_fft_result = np.fft.fft(c_complex, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Upon running the code cell below, we can see that there are no negative components in the frequency domain. This exponential carrier is simply a signal at 900 MHz and does not contain any symmetry in the frequency domain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(20,4))\n",
    "axs[0].plot(c_complex.real[:100])\n",
    "axs[0].plot(c_complex.imag[:100])\n",
    "axs[0].set_title('Time Domain Complex Carrier')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "axs[0].legend(('I Component', 'Q Component'))\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft)]/1e6, np.abs(c_complex_fft_result[:int(N_fft)]))\n",
    "axs[1].set_title('Two Sided FFT plot of the Complex Carrier')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This modulator creates the following signal, achieved by multiplying $g(t)$ with $e^{j 2 \\pi f_{c} t}$, as below.\n",
    "\n",
    "$$\n",
    "v(t) = g(t)e^{j 2 \\pi f_{c} t} = [g_{1}(t) + jg_{2}(t)]e^{j 2 \\pi f_{c} t}\n",
    "$$\n",
    "\n",
    "We know that Euler's Formula relates to the trigonometric world such that\n",
    "\n",
    "$$\n",
    "e^{j 2 \\pi f_{c} t} = \\cos(2 \\pi f_{c} t) + j \\sin(2 \\pi f_{c} t).\n",
    "$$\n",
    "\n",
    "Therefore,\n",
    "\n",
    "$$\n",
    "v(t) = g(t)e^{j 2 \\pi f_{c} t} = [g_{1}(t) + jg_{2}(t)][\\cos(2 \\pi f_{c} t) + j \\sin(2 \\pi f_{c} t)],\n",
    "$$\n",
    "\n",
    "$$\n",
    "v(t) = g_{1}(t)\\cos(2 \\pi f_{c} t) + jg_{2}(t)\\cos(2 \\pi f_{c} t) + jg_{1}(t)\\sin(2 \\pi f_{c} t) - g_{2}(t)\\sin(2 \\pi f_{c} t).\n",
    "$$\n",
    "\n",
    "We can now extract the real and imaginary components, which gives\n",
    "\n",
    "$$\n",
    "v(t) = [g_{1}(t) \\cos (2 \\pi f_{c} t) - g_{2}(t) \\sin (2 \\pi f_{c} t)] + j [g_{1}(t) \\sin (2 \\pi f_{c} t) + g_{2}(t) \\cos(2 \\pi f_{c} t)].\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can do this sum in Python using a simple multiplication of the complex information signal with the complex exponential."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find complex modulated signal v(t)\n",
    "v = g_complex*c_complex"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of real modulated signal\n",
    "v_fft_result = np.fft.fft(v, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we might expect from the exponential carrier, modulation results in positive frequency components only. Therefore, we are able see frequency components at $f_{c} - f_{b}$ and $f_{c} + f_{b}$ for each signal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(20,4))\n",
    "axs[0].plot(v.real[:200])\n",
    "axs[0].plot(v.imag[:200])\n",
    "axs[0].set_title('Time Domain Complex Carrier Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "axs[0].legend(('I signal Component', 'Q Signal Component'))\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft)]/1e6, np.abs(v_fft_result[:int(N_fft)]))\n",
    "axs[1].set_title('Two Sided FFT plot of the Complex Modulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Information about both $g_{1}(t)$ and $g_{2}(t)$ is contained in the real portion of the signal and as a result the imaginary part can be removed using a 'Real' operator, prior to transmission. This operation gives us,\n",
    "\n",
    "$$\n",
    "y(t) = g_{1}(t) \\cos (2 \\pi f_{c} t) - g_{2}(t) \\sin (2 \\pi f_{c} t).\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find Real modulated signal\n",
    "y = v.real"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of real modulated signal\n",
    "y_fft_result = np.fft.fft(y, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can observe from the plots below that the modulated signal is identical to that produced by the QAM model presented in the previous notebook. Note that a one-sided spectrum is shown here, as the signal is real."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(20,4))\n",
    "axs[0].plot(y[:200])\n",
    "axs[0].set_title('Time Domain Real Modulated Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(y_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Real Modulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Complex Demodulation <a class=\"anchor\" id=\"complex_QAM_demod\"></a>\n",
    "\n",
    "QAM Demodulation can also be expressed using complex notation, shown in [Figure 2](#fig-2).:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a class=\"anchor\" id=\"fig-2\"></a>\n",
    "<figure>\n",
    "<img src='./images/complex_demodulation.svg' height='60%' width='60%'/>\n",
    "    <figcaption><b>Figure 2: Complex demodulation architecture.</b></figcaption>\n",
    "</figure>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We aim to demodulate the received signal, or 'shift' the spectrum to the left. Therefore our local oscillator should be the negative of the transmitter's oscillator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define local exponential carrier at the receiver\n",
    "complex_demod_carrier = np.exp(-1j*2*np.pi*fc*t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of real modulated signal\n",
    "complex_demod_carrier_fft_result = np.fft.fft(complex_demod_carrier, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can confirm in the frequency domain that the local oscillator is negative as we see a single frequency component at -900 MHz. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(20,4))\n",
    "axs[0].plot(complex_demod_carrier.real[:100])\n",
    "axs[0].plot(complex_demod_carrier.imag[:100])\n",
    "axs[0].set_title('Time Domain Complex Carrier')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "axs[0].legend(('I Component', 'Q Component'))\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft)]/1e6, np.abs(complex_demod_carrier_fft_result[:int(N_fft)]))\n",
    "axs[1].set_title('Two Sided FFT plot of the Complex Carrier')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We multiply the received signal $y_{t}$ with the local oscillator, which gives\n",
    "\n",
    "$$\n",
    "x(t) = y(t)e^{-j 2 \\pi f_{c} t} = [g_{1}(t) \\cos (2 \\pi f_{c} t) - g_{2}(t) \\sin (2 \\pi f_{c} t)]e^{-j 2 \\pi f_{c} t},\n",
    "$$\n",
    "\n",
    "$$\n",
    "x(t) = [g_{1}(t) \\cos (2 \\pi f_{c} t) - g_{2}(t) \\sin (2 \\pi f_{c} t)] [\\cos (2 \\pi f_{c} t) - j\\sin (2 \\pi f_{c} t)].\n",
    "$$\n",
    "\n",
    "Expanding the square brackets above and reordering gives\n",
    "\n",
    "$$\n",
    "x(t) = g_{1}(t) \\cos ^2(2 \\pi f_{c} t) + j\\sin ^2 (2 \\pi f_{c} t) - jg_{1}(t)\\cos (2 \\pi f_{c} t)\\sin (2 \\pi f_{c} t) - g_{2}(t)\\sin (2 \\pi f_{c} t)\\cos (2 \\pi f_{c} t).\n",
    "$$\n",
    "\n",
    "Using trigonometric identities we can reduce this to\n",
    "\n",
    "$$\n",
    "x(t) = 0.5[g_{1}(t) + jg_{2}] + 0.5(g_{1}(t) \\cos(4 \\pi f_{c} t) - j0.5(g_{2}(t) \\cos(4 \\pi f_{c} t) - j0.5(g_{1}(t) \\sin(4 \\pi f_{c} t) - 0.5(g_{2}(t) \\sin(4 \\pi f_{c} t).\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Demodulate by multiplying y(t) by the exponential\n",
    "x = y*complex_demod_carrier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of demodulated signals\n",
    "x_fft_result = np.fft.fft(x, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the frequency domain, we can observe that the desired signal has been shifted and is now centred around 0 Hz, i.e. it has been successfully demodulated. However, a second copy of the signal can also be seen, centred around $-2 f_{c}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(20,4))\n",
    "axs[0].plot(x.real[:200])\n",
    "axs[0].plot(x.imag[:200])\n",
    "axs[0].set_title('Time Domain Complex Demodulated Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "axs[0].legend(('I Demodulated Signal', 'Q Demodulated Signal'))\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft)]/1e6, np.abs(x_fft_result[:int(N_fft)]))\n",
    "axs[1].set_title('Two Sided FFT plot of the Complex Demodulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In a similar way to the previous example, we can apply a low pass filter to this signal to remove the unwanted components. The lowpass filter is symmetrical and so it will remove the unwanted components centred at $-2 f_{c}$, which gives\n",
    "\n",
    "$$\n",
    "z(t) = 0.5[g_{1}(t) + jg_{2}].\n",
    "$$\n",
    "\n",
    "In other words, the original \"information\" signal that was transmitted has been retrieved, but it is scaled by 0.5."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define lowpass filter\n",
    "f_cutoff = 0.1 # Cutoff frequency as a fraction of the sampling rate\n",
    "b = 0.08  # Transition band, as a fraction of the sampling rate (in (0, 0.5)).\n",
    "\n",
    "N = int(np.ceil((4 / b)))\n",
    "if not N % 2: N += 1  # N is odd.\n",
    "n = np.arange(N)\n",
    "\n",
    "h = np.sinc(2 * f_cutoff * (n - (N - 1) / 2)) # Compute sinc filter.\n",
    "w = np.blackman(N) # Compute Blackman window.\n",
    "h = h * w # Multiply sinc filter by window.\n",
    "h = h / np.sum(h) # Normalize to get unity gain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Apply filter to demodulated signal\n",
    "z = np.convolve(x, h)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of filtered signal\n",
    "z_fft_result = np.fft.fft(z, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Upon running the code cell below, we see that the reconstructed signal matches the input signal in both time and frequency domains."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,3, figsize=(20,4))\n",
    "axs[0].plot(z.real[:200])\n",
    "axs[0].plot(z.imag[:200])\n",
    "axs[0].set_title('Time Domain Lowpass \\nFiltered Complex Demodulated Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "axs[0].legend(('I Signal Component', 'Q Signal Component'))\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(z_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Lowpass \\nFiltered Complex Demodulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()\n",
    "\n",
    "axs[2].plot(freqs[:int(N_fft)]/1e6, np.abs(z_fft_result[:int(N_fft)]))\n",
    "axs[2].set_title('Two Sided FFT plot of the Lowpass\\n Filtered Complex Demodulated Signal')\n",
    "axs[2].set_ylabel('Magnitude')\n",
    "axs[2].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can therefore confirm that the complex modulation and demodulation presented has been successful, and moreover it has produced the same results as the modulation / demodulation from the previous notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Conclusion <a class=\"anchor\" id=\"conclusion\"></a>\n",
    "In this notebook, we have explored a complex QAM architecture, and demonstrated how we can exploit the properties of a complex exponential mixer to modulate and demodulate a complex information signal. This method produces exactly equivalent results to the real QAM modulation and demodulation presented in the previous notebook, but it can be more concisely represented, both diagrammatically and mathematically. Working with the complex mathematical notation is also more tractable.\n",
    "\n",
    "The next notebook will explore the topic of frequency planning in wireless communication systems."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "[⬅️ Previous Notebook](02_qam_modulation.ipynb) || [Next Notebook 🚀](../notebook_F/01_frequency_planning.ipynb)\n",
    "\n",
    "Copyright © 2023 Strathclyde Academic Media\n",
    "\n",
    "---\n",
    "---"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
